const path = require('path')
// 获取设备cpu内核数
const os = require('os')
// eslint
const ESLintWebpackPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// 多进程打包
const TerserPlugin = require('terser-webpack-plugin')
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin')

// cpu核数
const threads = os.cpus().length

// 获取处理样式的Loaders
const getStyleLoaders = preProcessor => {
  return [
    MiniCssExtractPlugin.loader, // 取代早期的style-loader，这个loader可以提取css代码成为单独的文件
    // 这里有两种不同的规定loader的写法
    // 第一种是直接写loader的名字，就是一个字符串，这个就只是规定了用哪个loader
    'css-loader',
    // 第二种写法就是下面这个对象的格式，可以使用options配置项，用来规定一些配置
    // 就是对这个loader的用法和处理细节加一些自己的配置
    {
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: [
            'postcss-preset-env' // 能解决大多数样式兼容性问题
          ]
        }
      }
    },
    preProcessor
  ].filter(Boolean)
}

module.exports = {
  // 入口
  // 单入口
  entry: './src/main.js',
  // 多入口
  // entry: {
  //   main: './src/main.js',
  //   app: './src/app.js'
  // }, //相对路径
  // 输出
  output: {
    // path: 文件输出目录，必须是绝对路径
    // path.resolve()方法返回一个绝对路径
    // __dirname 当前文件的文件夹绝对路径
    path: path.resolve(__dirname, '../dist'), // 绝对路径 生产模式需要输出
    // filename: 输出文件名
    // [name]是webpack命名规则，使用chunk的name作为输出的文件名。
    // 什么是chunk？打包的资源就是chunk，输出出去叫bundle。
    // chunk的name是啥呢？ 比如： entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx，和文件名无关。
    // 为什么需要这样命名呢？如果还是之前写法main.js，那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
    // filename: 'static/js/main.js', // 将 js 文件输出到 static/js 目录中
    // filename: 'js/[name].js',
    filename: 'static/js/[name].[contenthash:10].js', // 入口文件打包输出资源命名方式
    chunkFilename: 'static/js/[name].chunk.[contenthash:10].js', // 动态导入输出资源命名方式
    assetModuleFilename: 'static/media/[name].[hash][ext]', // 图片、字体等资源命名方式（注意用hash）
    clean: true // 自动将上次打包目录资源清空
  },
  // 加载器
  module: {
    rules: [
      {
        oneOf: [
          {
            test: /\.css$/i, // 只检测css文件
            // use 数组里面 Loader 执行顺序是从右到左
            use: getStyleLoaders()
          },
          {
            test: /\.less$/,
            use: getStyleLoaders('less-loader')
          },
          {
            test: /\.s[ac]ss$/,
            use: getStyleLoaders('sass-loader')
          },
          {
            test: /\.styl$/,
            use: getStyleLoaders('stylus-loader')
          },
          {
            test: /\.(png|jpe?g|gif|webp)$/,
            type: 'asset',
            // 这个功能已经内置了，默认就是把小于10kb的直接给使用base64处理
            parser: {
              dataUrlCondition: {
                maxSize: 40 * 1024 // 小于40kb的图片会被base64处理
              }
            }
            // 统一采用顶部配置
            // generator: {
            //   // 将图片文件输出到 static/imgs 目录中
            //   // 将图片文件命名 [hash:8][ext][query]
            //   // [hash:8]: hash值取8位
            //   // [ext]: 使用之前的文件扩展名
            //   // [query]: 添加之前的query参数
            //   filename: 'static/imgs/[hash:8][ext][query]'
            // }
          },
          {
            test: /\.(ttf|woff2?|map4|map3|avi)$/,
            type: 'asset/resource'
            // generator: {
            //   filename: 'static/media/[hash:8][ext][query]'
            // }
          },
          {
            test: /\.js$/,
            // exclude: /node_modules/, // 排除node_modules代码不编译
            include: path.resolve(__dirname, '../src'), // 也可以用包含
            use: [
              {
                loader: 'thread-loader', // 开启多进程
                options: {
                  workers: threads // 数量
                }
              },
              {
                loader: 'babel-loader',
                options: {
                  cacheDirectory: true, // 开启babel编译缓存
                  cacheCompression: false, // 缓存文件不要压缩
                  plugins: ['@babel/plugin-transform-runtime'] // 减少代码体积
                }
              },
              {
                // 注意自定义loader的路径问题，可以参考下面的文章
                // https://juejin.cn/post/6891649726656020493
                loader: path.resolve(__dirname, '../loaders/clean-log-loader.js')
              }
            ]
            // loader: 'babel-loader',
            // options: {
            //   cacheDirectory: true, // 开启babel编译缓存
            //   cacheCompression: false // 缓存文件不要压缩
            // }
          }
        ]
      }
    ]
  },
  // 插件
  plugins: [
    new ESLintWebpackPlugin({
      // 指定检查文件的根目录
      context: path.resolve(__dirname, '../src'),
      exclude: 'node_modules', // 默认值
      cache: true, // 开启缓存
      // 缓存目录
      cacheLocation: path.resolve(
        __dirname,
        '../node_modules/.cache/.eslintcache'
      ),
      threads // 开启多进程
    }),
    new HtmlWebpackPlugin({
      // 以 public/index.html 为模板创建文件
      // 新的html文件有两个特点：1. 内容和源文件一致 2. 自动引入打包生成的js等资源
      template: path.resolve(__dirname, '../public/index.html')
    }),
    // 提取css成单独文件的loader，modules中配置好之后，这里还需要调用一次
    new MiniCssExtractPlugin({
      // 定义输出文件名和目录
      filename: 'static/css/[name].[contenthash:10].css',
      chunkFilename: 'static/css/[name].chunk.[contenthash:10].css'
    }),
    // css压缩
    // new CssMinimizerPlugin()
    // 浏览器设置缓存
    new PreloadWebpackPlugin({
      rel: 'preload', // preload兼容性更好
      as: 'script'
      // rel: 'prefetch' // prefetch兼容性更差
    })
  ],
  optimization: {
    minimize: true,
    // 压缩的配置
    minimizer: [
      // css压缩也可以写到optimization.minimizer里面，效果一样的
      new CssMinimizerPlugin(),
      // 当生产模式会默认开启TerserPlugin，但是我们需要进行其他配置，就要重新写了
      new TerserPlugin({
        parallel: threads // 开启多进程
      }),
      // 压缩图片
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminGenerate,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              [
                'svgo',
                {
                  plugins: [
                    'preset-default',
                    'prefixIds',
                    {
                      name: 'sortAttrs',
                      params: {
                        xmlnsOrder: 'alphabetical'
                      }
                    }
                  ]
                }
              ]
            ]
          }
        }
      })
    ],
    // 代码分割配置
    splitChunks: {
      chunks: 'all' // 对所有模块都进行分割
      // 以下是默认值
      // minSize: 20000, // 分割代码最小的大小
      // minRemainingSize: 0, // 类似于minSize，最后确保提取的文件大小不能为0
      // minChunks: 1, // 至少被引用的次数，满足条件才会代码分割
      // maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
      // maxInitialRequests: 30, // 入口js文件最大并行请求数量
      // enforceSizeThreshold: 50000, // 超过50kb一定会单独打包（此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests）
      // cacheGroups: { // 组，哪些模块要打包到一个组
      //   defaultVendors: { // 组名
      //     test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //     priority: -10, // 权重（越大越高）
      //     reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块，则它将被重用，而不是生成新的模块
      //   },
      //   default: { // 其他没有写的配置会使用上面的默认值
      //     minChunks: 2, // 这里的minChunks权重更大
      //     priority: -20,
      //     reuseExistingChunk: true,
      //   },
      // },
      // 修改配置
      // cacheGroups: {
      //   // 组，哪些模块要打包到一个组
      //   // defaultVendors: { // 组名
      //   //   test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
      //   //   priority: -10, // 权重（越大越高）
      //   //   reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块，则它将被重用，而不是生成新的模块
      //   // },
      //   default: {
      //     // 其他没有写的配置会使用上面的默认值
      //     minSize: 0, // 我们定义的文件体积太小了，所以要改打包的最小文件体积
      //     minChunks: 2,
      //     priority: -20,
      //     reuseExistingChunk: true
      //   }
      // }
    },
    // 提取runtime文件
    runtimeChunk: {
      name: entrypoint => `runtime~${entrypoint.name}` // runtime文件命名规则
    }
  },
  // 开发服务器
  // devServer: {
  //   host: 'localhost', // 启动服务器域名
  //   port: '3000', // 启动服务器端口号
  //   open: true // 是否自动打开浏览器
  // },
  // 模式 development production
  mode: 'production'
  // devtool: 'source-map'
}
